package middlewares

import (
	"bytes"
	"io"
	"regexp"
	"strings"
	"time"

	"gitee.com/xxhsjp/x-core/conf"
	"github.com/gin-gonic/gin"
	log "github.com/sirupsen/logrus"
)

type RespWriterWrapper struct {
	gin.ResponseWriter
	body *bytes.Buffer
}

func (w RespWriterWrapper) Write(b []byte) (int, error) {
	w.body.Write(b)
	return w.ResponseWriter.Write(b)
}

func (w RespWriterWrapper) WriteString(s string) (int, error) {
	w.body.WriteString(s)
	return w.ResponseWriter.WriteString(s)
}

func Access() gin.HandlerFunc {
	var reSlice []*regexp.Regexp
	urlEpxs := conf.Wrapper.GetStringSlice("server.ignoreRespLogExps")
	if l := len(urlEpxs); l > 0 {
		reSlice = make([]*regexp.Regexp, l)
		for i := 0; i < l; i++ {
			reSlice[i] = regexp.MustCompile(urlEpxs[i])
		}
	}

	return func(ctx *gin.Context) {
		t := time.Now()
		uri := ctx.Request.URL.Path

		var respBody string
		var rww RespWriterWrapper
		if ignoreRespLogUri(uri, reSlice) {
			respBody = "null"
		} else {
			rww = RespWriterWrapper{ResponseWriter: ctx.Writer, body: bytes.NewBufferString("")}
			ctx.Writer = rww
		}

		var reqBody string
		if isMultipartContent(ctx.GetHeader("Content-Type")) {
			reqBody = "null"
		} else {
			body, _ := io.ReadAll(ctx.Request.Body)
			ctx.Request.Body = io.NopCloser(bytes.NewBuffer(body))
			reqBody = string(body)
		}

		accessLog := log.WithFields(log.Fields{
			"serialNo": ctx.GetString("serialNumber"),
			"user":     ctx.GetString("user"),
			"ip":       ctx.ClientIP(),
			"url":      ctx.Request.URL.String(),
			"reqBody":  reqBody,
		})

		accessLog.Info("request")

		ctx.Next()

		latency := time.Since(t)
		status := ctx.Writer.Status()
		if respBody != "null" {
			respBody = rww.body.String()
		}
		accessLog.WithFields(log.Fields{
			"status":   status,
			"latency":  latency,
			"respBody": respBody,
		}).Info("response")
	}
}

func ignoreRespLogUri(url string, res []*regexp.Regexp) bool {
	if len(res) > 0 {
		for _, re := range res {
			if re.MatchString(url) {
				return true
			}
		}
	}

	return false
}

func isMultipartContent(contentType string) bool {
	if contentType == "" {
		return false
	}

	return strings.HasPrefix(strings.ToLower(contentType), "multipart")
}
